library ChatCommand uses StringIterator
/*

    Made by: edo494
    Version: 1.2
   
    Requires: StringIterator - http://www.hiveworkshop.com/forums/jass-resources-412/snippet-stringiterator-246143/
   
    This Library provides very easy way to create game modes
    accessed via chat.
   
    There is currently no way to group multiple modes into
    one internally like in dota, where -amap means
    -allmid -allpick, but can be done externally by user.
         
    If you register 2 switches, and both appear in one
    line (-what -ever), it will always trigger them in order
    of appearance in the chat(function registered to swtich -what
    runs, then function registered to swtich -ever runs)
           
    Currently, even if you type something sensible and then
    type some valid switch, it will get triggered. This may
    be filtered, but requires some thinking of doing it inteligently.
    
    Example: "hey boys, its nice to -play with you" will still try
             to run callback to switch "play" with string "with you"
                     
    This resource should be safe for recursive execution, which means
    that if in some way to get to trigger the checker from the event
    registered to this resource, it should work properly
   
    API:
           
    function AddChatCommand takes string modeString, code callBack returns nothing
        - registers new Mode(mode1 in -mode1 etc) and adds callBack object
          into it for custom functionality
        - please note that "-" is not requested, because it is prepended
          to the switch
          Example: AddModeString("what", function ...) will trigger when you
                   type "-what" in chat
                   
    function RemoveChatCommand takes string modeString returns nothing
        - removes the modeString from the system, so it will no longer be
          triggered
        - O(log n), worst case is O(n), where n is number of modeStrings
          registered
                   
    function SetChatCommandDelimiter takes string newDelimiter returns nothing
        - sets new delimiter for the system to look for in the strings
        - this can be any number of characters
          Note: if you use "" or null, it will always consider the whole
          message after the switch as ChatCommandString even if there is
          another Switch inside the line
         
             
    Getters:
   
        native GetTriggerPlayer takes nothing returns player
            - returns the player that entered the chat mode to chat
       
        function GetChatCommand takes nothing returns string
            - returns the string used as switch
              Example: "-what else" returns "what"
           
        function GetChatCommandString takes nothing returns string
            - returns the string that appears after the mode until
              another mode or until the end of string
              Example: "-what else or -if something" will return "else or"
                       or "something", dependsing when you request it.
       
        function GetChatCommandDelimiter takes nothing returns string
            - returns the delimiter string
            - defaults to "-"
             
             
        struct ChatCommand extends array
           
            static method command takes nothing returns string
                - returns the string used as switch(-what returns "what")
           
            static method sring takes nothing returns string
                - returns the string that appears after the mode until
                  another mode or until the end of string
           
            static method operator delimiter takes nothing returns string
                - returns the delimiter string
                - defaults to "-"
           
            static method operator delimiter= takes string newDelimiter returns nothing
                - sets new delimiter for the system to look for in the strings
                - this can be any number of characters
                  Note: if you use "" or null, it will always consider the whole
                  message after the switch as ChatCommandString even if there is
                  another Switch inside the line
*/
    globals
        private string array modes
        private trigger array trig
        private integer modesC = 0
        private string chatStringTrig = null
        private trigger globTrig = CreateTrigger()
        private string chatString = null
        private string modesDelimiter = "-"
    endglobals
   
    struct ChatCommand extends array
        static method command takes nothing returns string
            return chatStringTrig
        endmethod
       
        static method string takes nothing returns string
            return chatString
        endmethod
       
        static method operator delimiter takes nothing returns string
            return modesDelimiter
        endmethod
       
        static method operator delimiter= takes string newDelimiter returns nothing
            set modesDelimiter = newDelimiter
        endmethod
    endstruct
   
    function GetChatCommandString takes nothing returns string
        return chatString
    endfunction
   
    function GetChatCommand takes nothing returns string
        return chatStringTrig
    endfunction
   
    function GetChatCommandDelimiter takes nothing returns string
        return modesDelimiter
    endfunction
   
    function SetChatCommandDelimiter takes string newDelimiter returns nothing
        set modesDelimiter = newDelimiter
    endfunction
   
    function AddChatCommand takes string modeString, code callBack returns nothing
        local integer m = modesC
        if trig[m] == null and callBack != null then
            set trig[m] = CreateTrigger()
        endif
        set modes[m] = modeString
        call TriggerAddCondition(trig[m], Condition(callBack))
        set modesC = m + 1
    endfunction
   
    function RemoveChatCommand takes string modeString returns nothing
        local integer i = modesC - 1
        loop
            if modes[i] == modeString then
                call TriggerClearConditions(trig[i])
                set modes[i] = modes[modesC - 1]
                call DestroyTrigger(trig[i])
                set trig[i] = trig[modesC - 1]
                set modesC = i
                return
            endif
            exitwhen i == 0
            set i = i - 1
        endloop
    endfunction
   
    private module m
        private static method removeStartingSpaces takes string s returns string
            local integer i = 0
            local integer e = StringLength(s)-1
            local boolean b = false
            local string q = ""
            loop
                set q = SubString(s, i, i+1)
                if q != " " then
                    set b = true
                endif
                exitwhen i == e or b
                set i = i + 1
            endloop
            return SubString(s, i, e+1)
        endmethod
       
        private static method removeTrailingSpaces takes string s returns string
            local integer i = 0
            local integer e = StringLength(s) - 1
            local boolean b = false
           
            if e < 0 then
                return s
            endif
            loop
                if SubString(s, e, 1) == " " then
                    set e = e - 1
                else
                    set b = true
                endif
                exitwhen b or e <= 1
            endloop
            return SubString(s, 0, e + 1)
        endmethod
       
        private static method onChatEnter takes nothing returns boolean
            local string entered = GetEventPlayerChatString()
            local StringIterator iter = StringIterator.create(entered)
            local string helper = ""
            local string outer = ""
            local string inner = ""
            local integer looper = 0
            local string lock1 = ""
            local string lock2 = ""
            local string lock3 = ""
            local string endHelper = ""
            local string readDelimiter = ""
            local boolean equals = false
            loop
                call iter.setDelims(" ")
                exitwhen iter.end
                set helper = iter.read()
                set looper = 0
                set readDelimiter = SubString(helper, 0, StringLength(modesDelimiter))
                set equals = readDelimiter == modesDelimiter or /*
                                */ modesDelimiter == "" or modesDelimiter == null
                set outer = SubString(helper, StringLength(modesDelimiter), StringLength(helper))
                if equals then
                    loop
                        if outer == modes[looper] then
                            set lock1 = chatStringTrig
                            set chatStringTrig = outer
                            call iter.setDelims(modesDelimiter)
                            set inner = removeTrailingSpaces(removeStartingSpaces(iter.read()))
                            set lock2 = chatString
                            set lock3 = modesDelimiter
                            set chatString = inner
                            call TriggerEvaluate(trig[looper])
                            set chatStringTrig = lock1
                            set chatString = lock2
                            set modesDelimiter = lock3
                        endif
                        exitwhen looper == modesC - 1
                        set looper = looper + 1
                    endloop
                endif
            endloop
            return false
        endmethod
        private static method onInit takes nothing returns nothing
            local integer i = 0
            call TriggerAddCondition(globTrig, Condition(function thistype.onChatEnter))
            loop
                call TriggerRegisterPlayerChatEvent(globTrig, Player(i), "", false)
                exitwhen i == 15
                set i = i + 1
            endloop
        endmethod
    endmodule
   
    private struct s extends array
        implement m
    endstruct

endlibrary